package com.tbr.blockchain.block;

import com.alibaba.fastjson.JSON;
import com.tbr.blockchain.model.*;
import com.tbr.blockchain.p2p.Message;
import com.tbr.blockchain.p2p.P2PServer;
import com.tbr.blockchain.p2p.P2PService;
import com.tbr.blockchain.security.CryptoUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@Component
public class BlockService {

//    区块链存储结构
    private List<Block> blocksChain=new ArrayList<>();


//    当前节点钱包的集合
    private Map<String, Wallet> myWalletMap=new HashMap<>();

//    其他节点钱包集合，只包括公钥
    private Map<String,Wallet> otherWalletMap=new HashMap<>();

//    转账交易集合
    private List<Transaction> allTransaction=new ArrayList<>();

//    已打包的转账交易
    private List<Transaction> packedTransaction=new ArrayList<>();


    public BlockService() {
        Block beginBlock=new Block(1,"1",System.currentTimeMillis(),new ArrayList<Transaction>(),1,"1");
        blocksChain.add(beginBlock);
        System.out.println("生成创世纪块: "+JSON.toJSONString(beginBlock));
    }

    public BlockService(List<Block> blocksChain, Map<String, Wallet> myWalletMap, Map<String, Wallet> otherWalletMap, List<Transaction> allTransaction, List<Transaction> packedTransaction) {
        this.blocksChain = blocksChain;
        this.myWalletMap = myWalletMap;
        this.otherWalletMap = otherWalletMap;
        this.allTransaction = allTransaction;
        this.packedTransaction = packedTransaction;
    }

    public Block mine(String toAddress){
        allTransaction.add(newCoinbaseTx(toAddress));

       List<Transaction> blockTxs=new ArrayList<Transaction>(allTransaction);
       blockTxs.removeAll(packedTransaction);
        System.out.println(JSON.toJSONString(blockTxs));
        verifyAllTransactions(blockTxs);

        String newBlockHash="";
        int nonce=0;
        long start= System.currentTimeMillis();
        System.out.println("开始挖矿");
        while(true){
            newBlockHash= calculateHash(getLatestBlock().getHash(),blockTxs,nonce);
            if(isValidHash(newBlockHash)){
                System.out.println("挖矿完成，正确的Hash值: "+newBlockHash);
                System.out.println("挖矿耗费的时间："+(System.currentTimeMillis()-start)+"ms");
                break;
            }
            System.out.println("错误的hash值"+newBlockHash);
            nonce++;
        }

//        创建新区块
        Block block=createNewBlock(nonce,getLatestBlock().getHash(),newBlockHash,blockTxs);
        return block;

    }
    public void addTransaction(Transaction tx){
        allTransaction.add(tx);
    }

//    区块奖励
    private Transaction newCoinbaseTx(String toAddress) {
        TransactionInput input=new TransactionInput("0",-1,null,null);
        Wallet w1=myWalletMap.get(toAddress);
        TransactionOutput output=new TransactionOutput(10,w1.getHashPubKey());
        return new Transaction(CryptoUtil.UUID(),input,output);
    }

    //    校验
    private boolean isValidHash(String hash){
        return hash.startsWith("0000");
    }

//    计算区块的hash
    private String calculateHash(String previousHash,List<Transaction> currentTransactions,int nonce){
        return CryptoUtil.SHA256(previousHash+ JSON.toJSONString(currentTransactions)+nonce);
    }
//    创建新区块
    private Block createNewBlock(int nonce,String previousHash,String hash,List<Transaction> blockTxs){
        Block block=new Block(blocksChain.size()+1,hash,System.currentTimeMillis(),blockTxs,nonce,previousHash);
        if(addBlock(block)){
            return block;
        }
        return null;
    }

//    写入新区块
    public boolean addBlock(Block newblock){
        if(isValidNewHash(newblock,getLatestBlock())){
            blocksChain.add(newblock);

            packedTransaction.addAll(newblock.getTransactions());
            return true;
        }
        return false;
    }

//    返回最后一个区块
    public Block getLatestBlock(){
        return blocksChain.size()>0?blocksChain.get(blocksChain.size()-1):null;
    }

//    验证新区块是否有效
    public boolean isValidNewHash(Block newBlock,Block previousBlock){
        if(!previousBlock.getHash().equals(newBlock.getPreviousHash())){
            return false;
        }
        else {
            String hash=calculateHash(newBlock.getPreviousHash(),newBlock.getTransactions(),newBlock.getNonce());
            if(!hash.equals(newBlock.getHash())){
                System.out.println("新区块的hash无效："+hash+" "+newBlock.getHash());
                return false;
            }
            if(!isValidHash(newBlock.getHash())){
                return false;
            }
        }
        return true;
    }

//    验证所有交易是否有效，防止双花攻击
    private void verifyAllTransactions(List<Transaction> blockTxs){
        List<Transaction> invalidTxs=new ArrayList<>();
        for(Transaction tx: blockTxs){
            if(!verifyTransactions(tx)){
                invalidTxs.add(tx);
            }
        }
        blockTxs.removeAll(invalidTxs);
//        去除无效交易
        allTransaction.removeAll(invalidTxs);
    }

    private boolean verifyTransactions(Transaction tx) {
        if(tx.coinBaseTx()){
            return true;
        }

        Transaction preTx=findTransaction(tx.getTxIn().getTxId());
        return tx.verify(preTx);
    }

    private Transaction findTransaction(String txId) {
        for (Transaction tx: allTransaction){
            if(txId.equals(tx.getId())){
                return tx;
            }
        }
        return null;
    }


    //    创建交易
    public Transaction createTransaction(Wallet senderWallet,Wallet recipientWallet,int amount){
        List<Transaction> UTXOS=findUTXOS(senderWallet.getAddress());
        Transaction preTx=null;
        for(Transaction transaction: UTXOS){
            if(transaction.getTxOut().getValue()==amount){
                preTx=transaction;
                break;
            }
        }
        if(preTx==null){
            return null;
        }
        TransactionInput txIn=new TransactionInput(preTx.getId(),amount,null,senderWallet.getPublicKey());
        TransactionOutput txOut=new TransactionOutput(amount,recipientWallet.getHashPubKey());
        Transaction transaction=new Transaction(CryptoUtil.UUID(),txIn,txOut);
        transaction.sign(senderWallet.getPrivateKey(),preTx);
        allTransaction.add(transaction);
        return transaction;

    }
//    寻找UTXOS
    private List<Transaction> findUTXOS(String address) {
        List<Transaction> UTXOS=new ArrayList<Transaction>();
        Set<String> spendTxs=new HashSet<String>();
        for(Transaction transaction:allTransaction){
            if(transaction.coinBaseTx()){
                continue;
            }
            if(address.equals(Wallet.getAddress(transaction.getTxIn().getPublicKey()))){
                spendTxs.add(transaction.getTxIn().getTxId());
            }
        }

        for(Block block :blocksChain){
            List<Transaction> transactions=block.getTransactions();
            for(Transaction tx:transactions){
                if(address.equals(CryptoUtil.MD5(tx.getTxOut().getPublicKeyHash()))){
                    if(!spendTxs.contains(tx.getId())){
                        UTXOS.add(tx);
                    }
                }
            }
        }
        return UTXOS;

    }

//    创建钱包
   public Wallet createWallet(){
        Wallet wallet=  Wallet.generateWallet();
        String address=wallet.getAddress();
        myWalletMap.put(address,wallet);
        return wallet;
   }

//    获取钱包余额
    public int getWalletBalance(String address){
        List<Transaction> UTXOS=findUTXOS(address);
        int balance=0;
        for(Transaction transaction :UTXOS){
            balance+=transaction.getTxOut().getValue();
        }
        return balance;
    }

    /**
     * 获取
     * @return blocksChain
     */
    public List<Block> getBlocksChain() {
        return blocksChain;
    }

    /**
     * 设置
     * @param blocksChain
     */
    public void setBlocksChain(List<Block> blocksChain) {
        this.blocksChain = blocksChain;
    }

    /**
     * 获取
     * @return myWalletMap
     */
    public Map<String, Wallet> getMyWalletMap() {
        return myWalletMap;
    }

    /**
     * 设置
     * @param myWalletMap
     */
    public void setMyWalletMap(Map<String, Wallet> myWalletMap) {
        this.myWalletMap = myWalletMap;
    }

    /**
     * 获取
     * @return otherWalletMap
     */
    public Map<String, Wallet> getOtherWalletMap() {
        return otherWalletMap;
    }

    /**
     * 设置
     * @param otherWalletMap
     */
    public void setOtherWalletMap(Map<String, Wallet> otherWalletMap) {
        this.otherWalletMap = otherWalletMap;
    }

    /**
     * 获取
     * @return allTransaction
     */
    public List<Transaction> getAllTransaction() {
        return allTransaction;
    }

    /**
     * 设置
     * @param allTransaction
     */
    public void setAllTransaction(List<Transaction> allTransaction) {
        this.allTransaction = allTransaction;
    }

    /**
     * 获取
     * @return packedTransaction
     */
    public List<Transaction> getPackedTransaction() {
        return packedTransaction;
    }

    /**
     * 设置
     * @param packedTransaction
     */
    public void setPackedTransaction(List<Transaction> packedTransaction) {
        this.packedTransaction = packedTransaction;
    }

    public String toString() {
        return "BlockService{blocksChain = " + blocksChain + ", myWalletMap = " + myWalletMap + ", otherWalletMap = " + otherWalletMap + ", allTransaction = " + allTransaction + ", packedTransaction = " + packedTransaction + "}";
    }

    public void replaceChain(List<Block> newBlock) {
       if(isValidChain(newBlock)&& newBlock.size()>blocksChain.size()){
           blocksChain=newBlock;
           packedTransaction.clear();
           blocksChain.forEach(block -> {
               packedTransaction.addAll((block.getTransactions()));
           });
       }else{
           System.out.println("接受的区块链无效");
       }
    }

    public boolean isValidChain(List<Block> chain){
        Block block=null;
        Block lastBlock=chain.get(0);
        int currentIndex=1;
        while(currentIndex<chain.size()){
            block=chain.get(currentIndex);
            if(!isValidNewHash(block,lastBlock)){
                return false;
            }
            lastBlock=block;
            currentIndex++;
        }
        return true;
    }

}
